// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prpc

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"unicode"
)

// This file implements "Accept" and "Accept-Encoding" HTTP header parser.
// Spec: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

// accept is a parsed "Accept" or "Accept-Encoding" HTTP header.
type accept []acceptItem

type acceptItem struct {
	Value         string // e.g. "application/json; encoding=utf-8"
	QualityFactor float32
}

// parseAccept parses an "Accept" or "Accept-Encoding" HTTP header.
//
// See spec http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
//
// This implementation is slow. Does not support accept params.
func parseAccept(v string) (accept, error) {
	if v == "" {
		return nil, nil
	}

	var result accept
	for _, t := range strings.Split(v, ",") {
		t = strings.TrimSpace(t)
		if t == "" {
			continue
		}

		value, q := qParamSplit(t)
		item := acceptItem{Value: value, QualityFactor: 1.0}
		if q != "" {
			qualityFactor, err := strconv.ParseFloat(q, 32)
			if err != nil {
				return nil, fmt.Errorf("q parameter: expected a floating-point number")
			}
			item.QualityFactor = float32(qualityFactor)
		}
		result = append(result, item)
	}
	return result, nil
}

// qParamSplit splits an acceptable item into value (e.g. media type) and
// the q parameter. Does not support accept extensions.
func qParamSplit(v string) (value string, q string) {
	rest := v
	for {
		semicolon := strings.IndexRune(rest, ';')
		if semicolon < 0 {
			value = v
			return
		}
		semicolonAbs := len(v) - len(rest) + semicolon // mark
		rest = rest[semicolon:]

		rest = rest[1:] // consume ;
		rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
		if rest == "" || (rest[0] != 'q' && rest[0] != 'Q') {
			continue
		}

		rest = rest[1:] // consume q
		rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
		if rest == "" || rest[0] != '=' {
			continue
		}

		rest = rest[1:] // consume =
		rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
		if rest == "" {
			continue
		}

		qValueStartAbs := len(v) - len(rest) // mark
		semicolon2 := strings.IndexRune(rest, ';')
		if semicolon2 >= 0 {
			semicolon2Abs := len(v) - len(rest) + semicolon2
			value = v[:semicolonAbs]
			q = v[qValueStartAbs:semicolon2Abs]
		} else {
			value = v[:semicolonAbs]
			q = v[qValueStartAbs:]
		}
		q = strings.TrimRightFunc(q, unicode.IsSpace)
		return
	}
}

// acceptFormat is a format specified in "Accept" header.
type acceptFormat struct {
	Format        Format
	QualityFactor float32 // preference, range: [0.0, 0.1]
}

// acceptFormatSlice is sortable by quality factor (desc) and format.
type acceptFormatSlice []acceptFormat

func (s acceptFormatSlice) Len() int {
	return len(s)
}

func (s acceptFormatSlice) Less(i, j int) bool {
	a, b := s[i], s[j]
	const epsilon = 0.000000001
	// quality factor descending
	if a.QualityFactor+epsilon > b.QualityFactor {
		return true
	}
	if a.QualityFactor+epsilon < b.QualityFactor {
		return false
	}
	// format ascending
	return a.Format < b.Format
}

func (s acceptFormatSlice) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// acceptsGZipResponse returns true if the server response body may be encoded
// with GZIP.
func acceptsGZipResponse(header http.Header) (bool, error) {
	accept, err := parseAccept(header.Get("Accept-Encoding"))
	if err != nil {
		return false, err
	}
	for _, a := range accept {
		if strings.EqualFold(a.Value, "gzip") {
			return true, nil
		}
	}
	return false, nil
}
